package com.zhong.learning.auto_paper.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.our.learning.infrastructure.constant.ResponseStatusEnum;
import com.our.learning.infrastructure.po.Paper;
import com.our.learning.infrastructure.pojo.Response;
import com.zhong.learning.auto_paper.dto.AutoPaperDTO;
import com.zhong.learning.auto_paper.dto.QuestionDTO;
import com.zhong.learning.auto_paper.mapper.AutoPaperMapper;
import com.zhong.learning.auto_paper.po.QuestionPO;
import com.zhong.learning.auto_paper.po.QuestionTypePO;
import com.zhong.learning.auto_paper.service.AutoPaperService;
import com.zhong.learning.auto_paper.service.QuestionPOService;
import com.zhong.learning.auto_paper.vo.PageVO;
import com.zhong.learning.auto_paper.vo.PaperVO;
import com.zhong.learning.auto_paper.vo.QuestionVO;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhong
 * @date 2023/6/18
 */
@Service
public class AutoPaperServiceImpl extends ServiceImpl<AutoPaperMapper, Paper> implements AutoPaperService {
    private final AutoPaperMapper autoPaperMapper;
    private final QuestionPOService questionPOService;

    public AutoPaperServiceImpl(AutoPaperMapper autoPaperMapper, QuestionPOService questionPOService) {
        this.autoPaperMapper = autoPaperMapper;
        this.questionPOService = questionPOService;
    }
    @Override
    public Response autoCompose(AutoPaperDTO autoPaperDto) {
        Integer difficulty = autoPaperDto.getDifficulty();
        Integer sumScore = autoPaperDto.getSumScore();
        if (difficulty == null || difficulty > 3) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("试卷难易度选择错误！");
        }
        int questionNum = 0;
        for (QuestionDTO question : autoPaperDto.getQuestions()) {
            questionNum += question.getNum();
        }
        if (sumScore == null || sumScore == 0) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("未设置试卷总分！");
        } else if (sumScore < questionNum) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("试卷总分需高于题目总数！");
        }
        List<QuestionDTO> questionDTOS = autoPaperDto.getQuestions();
        List<QuestionPO> questionList = new ArrayList<>();
        // 选取每个题型的题目
        for (QuestionDTO questionDTO : questionDTOS) {
            String type = questionDTO.getType();
            Integer num = questionDTO.getNum();
            List<QuestionPO> questionsOfOneType = getQuestionsOfOneType(type, num, difficulty);
            if (questionsOfOneType == null) {
                // 参数错误或题库数量不足
                return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("题库中没有足够与条件匹配的题目！");
            }
            questionList.addAll(questionsOfOneType);
        }
        // 设置试卷信息
        Paper paper = autoPaperDto.toAutoPaper();
        // 设置题目、分值、题号、答案
        List<QuestionVO> questionVOList = new ArrayList<>();
        List<String> answers = new ArrayList<>();
        int titleNo = 1;
        for (QuestionPO questionPO : questionList) {
            QuestionVO questionVO = new QuestionVO(questionPO);
            questionVO.setTitleNo(titleNo++);
            questionVOList.add(questionVO);
            answers.add(questionPO.getAnswer());
        }
        // 设置分值
        if (!setQuestionsScores(questionVOList, autoPaperDto.getSumScore())) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("无法分配分数，请合理设置！");
        }
        String questionJson = JSON.toJSONString(questionVOList);
        String answerJson = JSON.toJSONString(answers);
        paper.setQuestions(questionJson);
        paper.setAnswers(answerJson);
        // 存入试卷
        save(paper);
        int paperId = paper.getId();
        return new Response(ResponseStatusEnum.OK).addData("paperId", paperId);
    }

    @Override
    public Response getPaperById(Integer id) {
        LambdaQueryWrapper<Paper> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Objects.nonNull(id), Paper::getId, id);
        Paper paper = autoPaperMapper.selectOne(lambdaQueryWrapper);
        if (paper == null) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("查询不到对应ID的试卷");
        }
        PaperVO paperVO = new PaperVO(paper);
        return new Response(ResponseStatusEnum.OK).addData("paper", paperVO);
    }

    public Paper getPaperByIdLocal(Integer id) {
        LambdaQueryWrapper<Paper> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Objects.nonNull(id), Paper::getId, id);
        return autoPaperMapper.selectOne(lambdaQueryWrapper);
    }

    public Response getAllByPage(Integer page, Integer perPage, Integer type, Integer id) {
        IPage<Paper> iPage = new Page<>(page, perPage);
        LambdaQueryWrapper<Paper> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (id != null) {
            lambdaQueryWrapper.eq(Paper::getId, id);
        }
        if (type != null) {
            lambdaQueryWrapper.eq(Paper::getType, type);
        }
        iPage = autoPaperMapper.selectPage(iPage, lambdaQueryWrapper);
        long total = iPage.getTotal();
        List<Paper> papers = iPage.getRecords();
        List<PaperVO> paperVOs = new ArrayList<>();
        int index = (page - 1) * perPage + 1;
        for (Paper paper : papers) {
            if (paper == null) {
                return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("查询不到对应ID的试卷");
            }
            PaperVO paperVO = new PaperVO(paper);
            paperVO.setIndex(index++);
            paperVOs.add(paperVO);
        }
        PageVO pageVO = new PageVO(paperVOs, total);
        return new Response(ResponseStatusEnum.OK).addData("papers", pageVO);
    }

    @Override
    public Response getQuestionType() {
        List<QuestionTypePO> questionTypes = questionPOService.getQuestionTypes();
        if (questionTypes == null) {
            return new Response(ResponseStatusEnum.BAD_REQUEST).setMsg("题库暂时没有类型，请添加题型！");
        }
        return new Response(ResponseStatusEnum.OK).addData("questionTypes", questionTypes);
    }

    private List<QuestionPO> getQuestionsOfOneType(String type, Integer num, Integer difficulty) {
        if (difficulty > 3) return null;
        // 获取该题型题目列表
        List<QuestionPO> questions = questionPOService.getQuestionPOByType(type);
        if (num > questions.size()) {
            // 题库数量不足
            return null;
        } else if (num == questions.size()) {
            return questions.stream().sorted(Comparator.comparing(QuestionPO::getDifficulty))
                    .collect(Collectors.toList());
        }
        Map<Integer, Double> ratioMap = setRadioMap(difficulty);
        List<QuestionPO> res = new ArrayList<>();
        int nextNum = 0;
        Iterator<Map.Entry<Integer, Double>> iterator = ratioMap.entrySet().iterator();
        while (iterator.hasNext()) {
            if (res.size() == num) break;
            Map.Entry<Integer, Double> entry = iterator.next();
            Integer curDifficulty = entry.getKey();
            int curNum = nextNum;
            if (!iterator.hasNext()) curNum += num - res.size();
            else {
                curNum += (int) (entry.getValue() * num);
                if (curNum == 0) curNum++;
            }
            List<QuestionPO> list = questions.stream().filter(question -> curDifficulty.equals(question.getDifficulty()))
                    .collect(Collectors.toList());
            list = getRandomQuestionsOfDifficulty(list, curNum);
            if (list == null) {
                // 题目获取失败
                return null;
            }
            nextNum = curNum - list.size();
            res.addAll(list);
        }
        return res.stream().sorted(Comparator.comparing(QuestionPO::getDifficulty))
                .collect(Collectors.toList());
    }

    private Map<Integer, Double> setRadioMap(Integer difficulty) {
        // 设置试卷不同难易度的题目比例
        double[] ratio;
        int ratioLen = 3;
        if (difficulty == 1) {
            ratio = new double[]{0.8, 0.2, 0};
        } else if (difficulty == 2) {
            ratio = new double[]{0.2, 0.7, 0.1};
        } else if (difficulty == 3) {
            ratio = new double[]{0.1, 0.3, 0.6};
        } else {
            return null;
        }
        Map<Integer, Double> ratioMap = new HashMap<>();
        for (int i = 0; i < ratioLen; i++) {
            ratioMap.put(i + 1, ratio[i]);
        }
        Map<Integer, Double> resultMap = new LinkedHashMap<>();
        // 从比例最大的难易度开始分题
        ratioMap.entrySet()
                .stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .forEachOrdered(x -> resultMap.put(x.getKey(), x.getValue()));
        return resultMap;
    }

    private List<QuestionPO> getRandomQuestionsOfDifficulty(List<QuestionPO> questionPOS, Integer num) {
        if (questionPOS == null || num == null || num < 0) return null;
        if (num > questionPOS.size() * 2) {
            // 超出该比例题目数量太多
            return null;
        }
        if (num >= questionPOS.size()) {
            // 超出题库数量，直接全放
            return questionPOS;
        }
        List<QuestionPO> randomList = new ArrayList<>();
        if (num == 0) return randomList;
        Random random = new Random();
        for (int i = 0; i < num; i++) {
            int randomInt = random.nextInt(questionPOS.size() - 1);
            QuestionPO remove = questionPOS.remove(randomInt);
            randomList.add(remove);
        }
        return randomList;
    }

    private boolean setQuestionsScores(List<QuestionVO> list, int sumScore) {
        if (list == null) return false;
        int num = list.size();
        if (num == 0 || sumScore < num) {
            return false;
        }
        int a = sumScore / num;
        int b = sumScore % num;
        list.forEach(l -> l.setScore(a));
        if (b > 0) list.get(num - 1).setScore(a + b);
        return true;
    }
}